<?
	/**
	* @desc Encapsulates mysqli functions, handles various database related tasks
	*/
	class MySQLAbastraction {
		protected 	$CurrentDbConnection;
		private 	$rLastQuery;
		private 	$dbAccess;
		private		$bThrowExceptions = true;
		/** @var $insert_id Contains the last MySQL Insert ID */
		public		$insert_id;
		/** @var $affected_rows Contains the last MySQL Insert ID	*/
		public		$affected_rows;

		const 	TRIGGER_ERROR		= 1;

		/**
		* @desc Constructor for MysqlDatabase, opens connection to $host using $database with $user/$pass
		*/
		function __construct($tSettings = FALSE,$host = FALSE, $database = FALSE, $user = FALSE, $pass = FALSE){
			if(is_array($tSettings)){
				if(!is_set($tSettings['Host']) || !is_set($tSettings['Database']) || !is_set($tSettings['User']) || !is_set($tSettings['Password']) ){
					throw new DatabaseException("Missing Array entires when starting contructing database using array of options!",print_r($tSettings,1));
					return FALSE;
				}
				$database	=	$tSettings['Database'];
				$user		= 	$tSettings['User'];
				$pass		=	$tSettings['Password'];
				$host		=	$tSettings['Host'];
				
			}
			else if(!$host || !$database || !$user || !$pass){
				$sSettings = "Host:{$host} | Database:{$database} | User:{$user} | Pass:{$pass}";
				throw new DatabaseException("Unable to find all settings needed to start connection, SettingsFound={$sSetting}");
				return FALSE;
			}
				
				
			$port = 3306;
			
			if(preg_match('/^(.+):(.+)$/', $host, $tMatches)) {
				$host = $tMatches[1];
				$port = $tMatches[2];
			}
			$this->dbAccessSettings = array(
				'host'		=> $host,
				'database'	=> $database,
				'user'		=> $user,
				'pass'		=> $pass,
				'port'		=> $port,
			);
			$this->checkConnect();
		}
		function null_error_handler($a,$a,$a,$a) { }
		/**
		* @desc Ensures we have a connection to the database, primarily in place to allow for this object to be instantiated but not connected until needed
		*
		* @return boolean Indicating successfully connected
		*/
		private function checkConnect() {
			if(is_object($this->m_CurrentDbConnection))
				return true;

			if(!$this->ConnectToDB())
				return FALSE;
			$this->Connected = true;
			return true;
		}
		/**
		* 	Opens a connection to the DB
		*/
		function  ConnectToDB(){
			set_error_handler(array($this, 'null_error_handler'), E_ALL & ~E_NOTICE);

			if(($this->m_CurrentDbConnection = mysqli_connect($this->dbAccessSettings['host'], $this->dbAccessSettings['user'], $this->dbAccessSettings['pass'], $this->dbAccessSettings['database'], $this->dbAccessSettings['port'])) === false) {
				restore_error_handler();
				if(ConfigManager::IsDev())
					echo mysqli_connect_error().'  SQL ErrNo: '.mysqli_connect_errno().'<br>';
				if($this->bThrowExceptions)
					throw new DatabaseException('', mysqli_connect_error(), mysqli_connect_errno());
				else
					trigger_error(mysqli_connect_error().'  SQL ErrNo: '.mysqli_connect_errno(), E_USER_ERROR);
				return false;
			}
			restore_error_handler();
		}
		/**
		*	Closes the connection to the database
		*/
		public function CloseConnection() {
			if(is_object($this->m_CurrentDbConnection)) {
				mysqli_close($this->m_CurrentDbConnection);
			}
			unset($this->m_CurrentDbConnection);
		}
		/**
		*	Returns true if object is connected, if its not connected, it will try to connect
		*		and return false if it cannot connect
		*
		* @return boolean Indicating that we are connected or not
		*/
		public function IsConnected() {
			if(is_object($this->m_CurrentDbConnection))
				return mysqli_ping($this->m_CurrentDbConnection);
			else
				return $this->checkConnect();
		}
		/**
		* All queries to the database go through this function, it implements most of the core features
		*
		* @param string $sqlQuery The query to be executed
		* @return resource Returns result set from mysqli_query() statement.
		*/
		function sqlExecute($sqlQuery, $bDebugQuery=false){
			if($this->checkConnect()) {
				if(is_resource($this->rLastQuery))
					mysqli_free_result($this->rLastQuery);
				$sqlQuery = $this->TranslateQuery($sqlQuery);
				// While statement is for use with Automatic Retries, we break from the while loop when appropriate
				while(true) {
					$nQueryStartTime = microtime(true);
					// Perform the query
					
					if(is_callable($this->QueryPreProcessFunction))
						call_user_func($this->QueryPreProcessFunction, $sqlQuery);
					
					if(($this->rLastQuery = mysqli_query($this->m_DbConnection, trim($sqlQuery))) === false) {
						$this->TriggerError($sqlQuery);
						return false;
						
					} else {
						$this->LastQueryTime = microtime(true);
						$nQueryEndTime = microtime(true);
						// Break from loop
						break;
					}
				}
				$this->insert_id = mysqli_insert_id($this->m_DbConnection);
				$this->affected_rows = mysqli_affected_rows($this->m_DbConnection);
				return $this->rLastQuery;
			}
			return false;
		}
		/**
		* 	Translates Human date MM/DD/YYYY to MySQL Date YYYY-MM-DD
		*
		* @param string $sqlQuery The query which  may need manuipulation
		*/		
		public function TranslateQuery($sQuery) {
			// Translate Standard date "MM/DD/YYYY" to  MySQL Date Format "YYYY-MM-DD""
			$sQuery = preg_replace('/(\d{1,2})\/(\d{1,2})\/(\d{2,4})/', '\\3-\\1-\\2', $Query);
			return $sQuery;
		}
		/**
		*	Handles the errors generated by this object, either calling trigger_error() or throwing an exception
		* 	depending on the settings
		*
		* @param string $sqlQuery The query which generated an error
		*/
		function TriggerError($Query) {
			if($this->bThrowExceptions)
				throw new DatabaseException($Query, mysqli_error($this->m_CurrentDbConnection), mysqli_errno($this->m_CurrentDbConnection));
			else {
				$CleanQuery = str_replace(array(' ', "\t", "\r\n"), array('&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;', "<BR>\r\n"), $this->CleanQuery($Query));
				foreach(explode("\r\n", $CleanQuery) as $Index => $Line)
					$tLines[] = str_replace(' ', '&nbsp;', sprintf('%2.2s', $Index+1));
				$Lines = implode('<BR>', $tLines);
				trigger_error("Query Failed: ".mysqli_error($this->m_CurrentDbConnection)." (".mysqli_errno($this->m_CurrentDbConnection).")\r\n<br /><br /><div style=\"margin-left: 10px; float: left; width: 25px; font: 11px Courier New;\">{$Lines}</div><div style=\"float: left; font: 11px Courier New;\">{$CleanQuery}</div><div style=\"clear: both;\">&nbsp;</div>", E_USER_WARNING);
			}
		}
		/** Cleans up the query and puts in line numbers */
		private function CleanQuery($Query) {
			$tLines = explode("\r\n", $Query);
			$MinTabs = 99;
			for($j=1;$j<count($tLines);$j++) {	// Purposly ignores line 1
				if(preg_match('/^([\t]+)/', $tLines[$j], $tMatches)) {
					if(strlen($tMatches[1]) < $MinTabs)
						$MinTabs = strlen($tMatches[1]);
				}
			}
			for($j=0;$j<count($tLines);$j++) {
				if($j > 0)
					$tLines[$j] = substr($tLines[$j], $MinTabs+1, 99999999);
			}
			return implode("\r\n", $tLines);
		}
		/**
		* Loads a query into an array of associative arrays
		*
		* @param string $sqlQuery The query to be loaded into an array
		* @return array Returns an array with the results, or an empty array upon error or no data.
		*/
		function sqlLoadArray($sqlQuery, $bDebugQuery=false){
			if(($sqlResults = $this->sqlExecute($sqlQuery, $bDebugQuery)) != false) {
				if(mysqli_num_rows($sqlResults)==0)
					return array();
				$rowsArray = array();
				while ($row = mysqli_fetch_assoc ($sqlResults))
					$rowsArray[] = $row;
				mysqli_free_result($sqlResults);
				return $rowsArray;
			}
			return array();
		}
		/**
		* Loads a query into an array of associative arrays, indexed by the given columns
		*
		* @param string $sqlQuery The query to be loaded into an array
		* @param array $tIndexes - An array of column names by which the data will be returned in an indexed fashion
		* @return array Returns an array with the results, or an empty array upon error or no data.
		*/
		function sqlLoadIndexedArray($sqlQuery, $tIndexes=array(), $bDebugQuery=false){
			if(($sqlResults = $this->sqlExecute($sqlQuery, $bDebugQuery)) != false) {
				if(mysqli_num_rows($sqlResults)==0)
					return array();
				$dbArray = array();
				while ($row = mysqli_fetch_assoc ($sqlResults)) {
					$Ref = &$dbArray;
					foreach($tIndexes as $Index)
						$Ref = &$Ref[$row[$Index]];
					$Ref = $row;
				}
				mysqli_free_result($sqlResults);
				return $dbArray;
			}
			return array();
		}
		/**
		* Returns the named column(s) from the results of the query.  If multi-column is specified then an array of column values is returned
		*
		* @param string $sqlQuery The query to be loaded into an array
		* @param string $ColumnName The column to query for results
		* @return array|string Returns an array of column data, or a single value
		*/
		function sqlGetColumn($sqlQuery, $ColumnName, $bDebugQuery=false){
			foreach($this->sqlLoadArray($sqlQuery, $bDebugQuery) as $dbRow)
				$tReturn[] = $dbRow[$ColumnName];
			switch(count($tReturn)) {
				case 0:
				case 1:
					return $tReturn[0];
				default:
					return (array)$tReturn;
			}
		}
		/**
		* Forms an Select statement given the data
		*
		* @param string $TableName The name of the table for the INSERT statement
		* @param array $tblData The name/value pairs that are to be inserted
		* @return string Returns the formed INSERT statement
		*/
		function sqlStatement_Select($TableName, $sWhere){
			$sqlQuery = "SELECT *  FROM `$TableName` WHERE '".$sWhere."'";
			return $sqlQuery;
		}
		
		/**
		* Forms an INSERT statement given the data
		*
		* @param string $TableName The name of the table for the INSERT statement
		* @param array $tblData The name/value pairs that are to be inserted
		* @return string Returns the formed INSERT statement
		*/
		function sqlStatement_Insert($TableName, $tblData){
			$tValues = array();
			foreach ($tblData as $key => $Value)
				$tValues[] = $this->PrepareValueForMysql($key, $Value);
			$sqlQuery = "INSERT INTO `$TableName` SET ".implode(',',$tValues);
			return $sqlQuery;
		}
		/**
		* Forms an REPLACE statement given the data
		*
		* @param string $TableName The name of the table for the REPLACE statement
		* @param array $tblData The name/value pairs that are to be replaced
		* @return string Returns the formed REPLACE statement
		*/
		function sqlStatement_Replace($TableName, $tblData){
			$tSets = array();
			foreach ($tblData as $key => $Value)
				$tSets[] = $this->PrepareValueForMysql($key, $Value);
			$sqlQuery = "REPLACE INTO `$TableName` SET ".implode(',', $tSets);
			return $sqlQuery;
		}
		/**
		* Forms an UPDATE statement given the data
		*
		* @param string $TableName The name of the table for the UPDATE statement
		* @param array $tblData The name/value pairs that are to be updated
		* @param array $tblWhere The name/value pairs that are to form the WHERE statement
		* @return string Returns the formed UPDATE statement
		*/
		function sqlStatement_Update($TableName, $tblData, $tblWhere){
			$tSets = array();
			foreach ($tblData as $key => $Value)
				$tSets[] = $this->PrepareValueForMysql($key, $Value);
			$tWhere = array();
			foreach ($tblWhere as $key => $Value)
				$tWhere[] = $this->PrepareValueForMysql($key, $Value);
			$sqlQuery = "UPDATE `$TableName` SET ".implode(',',$tSets)." WHERE ".implode(' AND ',$tWhere);
			return $sqlQuery;
		}
		/**
		*	Used internally to form name='value' pairs of data
		* @param string $Name Name of the name/value pair
		* @param string $Value Value of the name/value pair
		* @return string Returns name/value pair ready for database
		*/
		private function PrepareValueForMysql($Name,$Value){
			switch(gettype($Value)){
				case "array":
					return "`$Name`=".$Value[0];
				case "double":
				case "integer":
					return "`$Name`=$Value";
				default:
					return "`$Name`='".addslashes($Value)."'";
			}
		}
		/**
		* Enables or Disables exception throwing for Database calls
		* @param boolean $bThrowExceptions Enables or disables exceptions
		*/
		public function SetThrowExceptions($bThrowExceptions) {$this->bThrowExceptions = $bThrowExceptions;}
		/**
		* @desc Wraps the mysql_real_escape_string function to ensure we're connected
		*/
		public function mysql_escape($Text) {
			if($this->checkConnect())
				return $this->m_CurrentDbConnection->escape_string($Text);

			return mysql_real_escape_string($Text);
		}
		/** Returns the result of 'FOUND_ROWS()' query for use with SELECT SQL_CALC_FOUND_ROWS() */
		public function GetFoundRows() {	return $this->sqlGetColumn('SELECT FOUND_ROWS() AS Rows', 'Rows');	}		
	
	
	}	
?>